using System;
using System.Collections.Generic;
using System.Threading;
using SS.Common.Logging;

namespace SS.Common.Threading.JobExecutor
{
    using SS.Common.Collection;

    #region Used namespaces

    

    #endregion

    /// <summary>
    /// Job executor that do the job sequencely
    /// Will not do the job by itself but delegate them to another executor
    /// </summary>
    /// <typeparam name="T">The type of the job</typeparam>
    public class SeqJobExecutor<T> : IJobExecutor<T>
        where T : class, IJob
    {
        /// <summary>
        /// Indicate the executor is running
        /// </summary>
        private const int RUNNING = 0;

        /// <summary>
        /// Indicate the executor is shutting down
        /// </summary>
        private const int SHUTDOWN = 1;

        /// <summary>
        /// Indicate the executor is stop
        /// </summary>
        private const int STOP = 2;

        /// <summary>
        /// Indicate the executor is terminated
        /// </summary>
        private const int TERMINATED = 3;

        /// <summary>
        /// Job jobQueue
        /// </summary>
        private readonly WaitBlockingCollection<T> jobQueue;

        /// <summary> 
        /// Queue size check interval
        /// </summary> 
        private int intervalQueueSizeCheck = 0;

        private static readonly ILogger Logger = LoggerManager.GetLogger(typeof(SeqJobExecutor<T>).Name);

        /// <summary>
        /// The main lock root
        /// </summary>
        private readonly object mainLock = new object();

        /// <summary>
        /// Lock object for termination event
        /// </summary>
        private readonly object terminationLock = new object();

        /// <summary>
        /// Blocking <see cref="Queue"/> call or not
        /// </summary>
        private volatile bool blockQueueWhenFull;

        /// <summary>
        /// Current running state (running, shutting down, stop, terminated)
        /// </summary>
        private volatile int runState;

        /// <summary>
        /// Flag indicate executing a job
        /// </summary>
        private bool executing;

        /// <summary>
        /// Reject handler, can be null
        /// </summary>
        private volatile ExecutorRejectHandler<T> rejectHandler;

        /// <summary>
        /// Exception handler, can be null
        /// </summary>
        private volatile ExecutorExceptionHandler<T> exceptionHandler;

        /// <summary>
        /// Gets or sets Block queue when full or not
        /// </summary>
        public bool BlockQueueWhenFull
        {
            get
            {
                return this.blockQueueWhenFull;
            }

            set
            {
                this.blockQueueWhenFull = value;
            }
        }

        /// <summary>
        /// Gets or sets the reject handler
        /// </summary>
        public ExecutorRejectHandler<T> RejectHandler
        {
            get
            {
                return this.rejectHandler;
            }

            set
            {
                this.rejectHandler = value;
            }
        }

        /// <summary>
        /// Gets or sets the exception handler
        /// </summary>
        public ExecutorExceptionHandler<T> ExceptionHandler
        {
            get
            {
                return this.exceptionHandler;
            }

            set
            {
                this.exceptionHandler = value;
            }
        }

        /// <summary>
        /// The wrapped executor to execute job
        /// </summary>
        private readonly IJobExecutor<IJob> wrappedExecutor;

        /// <summary>
        /// Initializes a new instance of the <see cref="SeqJobExecutor{T}"/> class. 
        /// </summary>
        /// <param name="wrappedExecutor">The wrapped executor to execute job</param>
        /// <param name="maximumQueueSize">Maxinum queue size</param>
        /// <param name="blockWhenFull">Block the enqueue thread when the queue is full or not</param>
        public SeqJobExecutor(
            IJobExecutor<IJob> wrappedExecutor, int maximumQueueSize, bool blockWhenFull)
        {
            this.wrappedExecutor = wrappedExecutor;
            this.BlockQueueWhenFull = blockWhenFull;
            this.jobQueue = new WaitBlockingCollection<T>(maximumQueueSize);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Stop(true);

            // Son Huynh - 2012-06-19
            this.wrappedExecutor.Dispose();
        }

        /// <summary>
        /// Initilize the executor
        /// </summary>
        public void Initialize()
        {
            // Son Huynh - 2012-06-19
            this.wrappedExecutor.Initialize();
        }

        /// <summary>
        /// Enqueue a job
        /// </summary>
        /// <param name="job">The job should be done</param>
        public void Queue(T job)
        {
            if (job == null)
            {
                throw new NullReferenceException("Job should not null");
            }

            if (this.runState >= STOP)
            {
                this.Reject(job, ExecutorRejectReason.ShuttingDown);
                return;
            }

            if (this.jobQueue.Count == 0)
            {
                this.TryExecuteJob(job);
            }
            else if (this.jobQueue.TryAdd(job))
            {
                this.EnsureQueueJobHandled();
            }
            else if (this.blockQueueWhenFull)
            {
                // Do not worry about waiting forever as when shutting down the queue will be drained
                this.jobQueue.Add(job);
                this.EnsureQueueJobHandled();
            }
            else if (this.runState >= STOP)
            {
                this.Reject(job, ExecutorRejectReason.ShuttingDown);
            }
            else
            {
                this.Reject(job, ExecutorRejectReason.TooBusy);
            }
        }

        /// <summary>
        /// Stop the executor by signal all workers
        /// </summary>
        /// <param name="force">
        /// Force exit don't wait for remain jobs processed
        /// </param>
        /// <returns>
        /// Remain jobs if force exit
        /// </returns>
        public List<T> Stop(bool force)
        {
            lock (this.mainLock)
            {
                List<T> remainJobs;
                var state = this.runState;
                if (force)
                {
                    if (state < STOP)
                    {
                        this.runState = STOP;
                    }

                    remainJobs = this.DrainQueue();
                }
                else
                {
                    if (state < SHUTDOWN)
                    {
                        this.runState = SHUTDOWN;
                    }

                    remainJobs = new List<T>();
                }

                // Terminate now if pool and queue empty
                try
                {
                    // Son Huynh - 2012-06-19
                     this.wrappedExecutor.Stop(force);
                }
                catch(Exception)
                {
                }
                this.TryTerminate();
                return remainJobs;
            }
        }

        /// <summary>
        /// Wait for the executor terminated
        /// </summary>
        /// <param name="timeout">Timeout icn milliseconds</param>
        /// <returns>True if the executor ternminated</returns>
        public bool WaitForTermination(int timeout)
        {
            lock (this.terminationLock)
            {
                if (this.runState == TERMINATED)
                {
                    return true;
                }

                try
                {
                    Monitor.Wait(this.terminationLock, timeout);
                }
                catch (ThreadAbortException)
                {
                    // Expected exception
                }
                catch (ThreadInterruptedException)
                {
                    // Expected exception
                }
                catch (ThreadStateException)
                {
                    // Expected exception
                }

                return this.runState == TERMINATED;
            }
        }

        /// <summary>
        /// Try to terminate the executor
        /// </summary>
        private void TryTerminate()
        {
            var state = this.runState;

            // Still have job to do
            if (state < STOP && this.jobQueue.Count > 0)
            {
                // disable termination check below
                state = RUNNING;
                this.EnsureQueueJobHandled();
            }

            if (state == STOP || state == SHUTDOWN)
            {
                this.runState = TERMINATED;
                lock (this.terminationLock)
                {
                    Monitor.PulseAll(this.terminationLock);
                }
            }
        }

        /// <summary>
        /// Reject a job with reason by call to reject handler
        /// </summary>
        /// <param name="job">The rejected job</param>
        /// <param name="reason">The rejected reason</param>
        private void Reject(T job, ExecutorRejectReason reason)
        {
            var handler = this.RejectHandler;
            if (handler != null)
            {
                handler.Invoke(this, job, reason);
            }
        }

        /// <summary>
        /// Try to execute a job if there is no job executing
        /// </summary>
        /// <param name="job">The job to try</param>
        private void TryExecuteJob(T job)
        {
            lock (this.mainLock)
            {
                // Is too late
                if (this.executing)
                {
                    this.jobQueue.Add(job);
                    this.EnsureQueueJobHandled();
                }
                else
                {
                    // Ok do the job now
                    this.executing = true;
                    var seqJob = new SeqJob(job, this);
                    this.wrappedExecutor.Queue(seqJob);
                }
            }
        }

        /// <summary>
        /// Ensure queued job handled
        /// </summary>
        private void EnsureQueueJobHandled()
        {
            lock (this.mainLock)
            {
                if (this.executing)
                {
                    return;
                }

                T job;
                if (this.jobQueue.TryRemove(out job))
                {
                    this.TryExecuteJob(job);
                }
            }
        }

        /// <summary>
        /// Drain the job queue
        /// </summary>
        /// <returns>List of remain jobs</returns>
        private List<T> DrainQueue()
        {
            var taskList = new List<T>();
            while (this.jobQueue.Count > 0)
            {
                var consumingEnum = this.jobQueue.GetConsumingEnumerable();
                foreach (var job in consumingEnum)
                {
                    taskList.Add(job);
                }
            }

            return taskList;
        }

        /// <summary>
        /// The helping job to do the job sequencely
        /// </summary>
        private class SeqJob : IJob
        {
            /// <summary>
            /// The wrapped job
            /// </summary>
            private readonly IJob job;

            /// <summary>
            /// The executor that create this seq job
            /// </summary>
            private readonly SeqJobExecutor<T> executor;

            /// <summary>
            /// Initializes a new instance of the <see cref="SeqJob"/> class.
            /// </summary>
            /// <param name="job">
            /// The wrapped job
            /// </param>
            /// <param name="executor">
            /// The executor that create this seq job
            /// </param>
            public SeqJob(IJob job, SeqJobExecutor<T> executor)
            {
                this.job = job;
                this.executor = executor;
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing,
            /// releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
            }

            /// <summary>
            /// Execute the real job, after the job then execute the next job
            /// </summary>
            public void Execute()
            {
                try
                {
                    if (this.job != null)
                    {
                        this.job.Execute();
                    } 
                }
                finally
                {
                    lock (this.executor.mainLock)
                    {
                        this.executor.executing = false;
                        this.executor.EnsureQueueJobHandled();
                    }
                }

                // 1 more job processed by SeqJobExecutor
                var count = Interlocked.Increment(ref executor.intervalQueueSizeCheck);
                if (count >= 2000)
                {
                    lock (executor)
                    {
                        Logger.InfoFormat("SeqJobExecutor Queue Size: {0}", executor.jobQueue.Count);
                        executor.intervalQueueSizeCheck = 0;
                    }
                }
            }
        }
    }
}
